  using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security;
using System.Text;
using System.Linq;
namespace RStein.Posterous.API
{
    internal class PosterousRequestBuilder : IPosterousRequestBuilder
    {
        #region Constants
        private static readonly string BOUNDARY_FORM_GUID = "486FBEAFD1CA4DCE821F830672B44A16";
        private static readonly string BEGIN_FORM_BOUNDARY = String.Format("\r\n--{0}\r\n", BOUNDARY_FORM_GUID);
        private static readonly string END_FORM_BOUNDARY = String.Format("\r\n--{0}--\r\n", BOUNDARY_FORM_GUID);

        private static readonly string REQUEST_CONTENT_TYPE = String.Format("multipart/form-data; boundary={0}",
                                                                             BOUNDARY_FORM_GUID);
        private static readonly string FORM_DATA_PATTERN  = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
        private static readonly string FORM_FILE_PATTERN = "Content-Disposition: form-data; name=\"media[]\"; filename=\"{0}\"\r\n Content-Type: application/octet-stream\r\n\r\n";
        private static readonly string HTTP_AUTH_HEADER_NAME = "Authorization";
        private static readonly string HTTP_AUTH_HEADER_VALUE_FORMAT = "basic {0}";
        #endregion Constants
        
        #region constructors
        public PosterousRequestBuilder()
        {
            m_requestStreamActions = new Queue<Action<Stream>>();
        }

        #endregion constructors
        #region private fields
        private HttpWebRequest m_webRequest;        
        private Queue<Action<Stream>> m_requestStreamActions;
        #endregion private fields

        #region properties
        public static string ApplicationName
        {
            get;
            set;
        }
        public static string ApplicationUrl
        {
            get;
            set;
        }
        #endregion properties
        #region methods
        public virtual void StartBuildWebRequest(string uri)
        {
            m_webRequest = WebRequest.Create(uri) as HttpWebRequest;            
            if (m_webRequest == null)
            {
                throw new ArgumentException();
            }
            
            #if !SILVERLIGHT
            m_webRequest.Timeout = Int32.MaxValue;
            #endif
            m_webRequest.Method = GlobalConstants.DEFAULT_HTTP_METHOD;
        }
        
        
        public virtual void BuildHttpAuthentication(string userName, string password)
        {
          var authHeader = String.Format("{0}:{1}", userName, password);
          var authHeaderBytes = Encoding.UTF8.GetBytes(authHeader);
          string authorizationHeaderValue = Convert.ToBase64String(authHeaderBytes);
          m_webRequest.Headers[HTTP_AUTH_HEADER_NAME] = String.Format(HTTP_AUTH_HEADER_VALUE_FORMAT, authorizationHeaderValue);

          //#if !SILVERLIGHT
          //m_webRequest.UseDefaultCredentials = false;
          //#endif

          //m_webRequest.Credentials = new NetworkCredential(userName, password);
        }
        
        public virtual void EndBuildWebRequest()
        {

        }
        
        public virtual WebRequest HttpWebRequest
        {
            get
            {
                checkRequestNotNull();
                return m_webRequest;
            }
        }
        
        public virtual void BuildFormData(string key, string value)
        {

            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            ensureContentType(); 
            m_requestStreamActions.Enqueue(stream =>
                                               {
                                                   string keyVal = String.Format(FORM_DATA_PATTERN, key, value);
                                                   var keyBytes = Encoding.UTF8.GetBytes(keyVal);                                                   
                                                   writeBoundaryBegin(stream);
                                                   stream.Write(keyBytes, 0, keyBytes.Length);                                                                                                      
                                               });            
        }
        
        protected virtual void OnWebRequestCreated(EventArgsValue<WebRequest> e)
        {
            EventHandler<EventArgsValue<WebRequest>> handler = WebRequestCreated;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnWebResponseCreated(EventArgsValue<WebResponse> e)
        {
            EventHandler<EventArgsValue<WebResponse>> handler = WebResponseCreated;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion methods
        #region IResponseBuilder

        private void writeBoundaryBegin(Stream stream)
        {
          ensureContentType();
            var endBoundaryBytes = Encoding.UTF8.GetBytes(BEGIN_FORM_BOUNDARY);
            stream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
        }

        private void ensureContentType()
        {
          m_webRequest.ContentType = REQUEST_CONTENT_TYPE;
        }

        public virtual void BuildFormDataFromFile(string fileName)
        {
            if (String.IsNullOrEmpty(fileName))
            {
                throw new ArgumentNullException("fileName");
            }

            if (!File.Exists(fileName))
            {
                throw new ArgumentException("file");
            }

            ensureContentType();
            m_requestStreamActions.Enqueue(stream =>
                                               {
                                                   string fName = Path.GetFileName(fileName);
                                                   writeMediaName(fName, stream);
                                                   using(Stream fileStream = File.OpenRead(fileName))
                                                   {
                                                       fileStream.CopyToStream(stream);   
                                                   }

                                               });
        }


        public virtual void BuildFormDataFromStream(Stream stream, string mediaName)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }
            if (string.IsNullOrEmpty(mediaName))
            {
                throw new ArgumentException("mediaName");
            }

            ensureContentType();
            m_requestStreamActions.Enqueue(reqStream =>
                                               {
                                                   writeMediaName(mediaName, reqStream);
                                                   stream.CopyToStream(reqStream);
                                               });
        }

        public virtual WebResponse GetResponse()
        {
            checkRequestNotNull();

            WebResponse response = null;

            finishRequest();
            
            OnWebRequestCreated(new EventArgsValue<WebRequest>(m_webRequest, null));
            try            
            {
                response = m_webRequest.GetResponse();
                OnWebResponseCreated(new EventArgsValue<WebResponse>(response, null));
            }
            catch (Exception e)
            {
                
                throw new PosterousException(GlobalConstants.INVALID_RESPONSE_EX, e);                 
            }

            return response;
            
        }

        private void finishRequest()
        {
            if (m_requestStreamActions.Count == 0)
            {
                return;
            }

            if (!String.IsNullOrEmpty(ApplicationName))
            {
                BuildFormData(GlobalConstants.REQ_APP_NAME, ApplicationName);
            }

            if (!String.IsNullOrEmpty(ApplicationUrl))
            {
                BuildFormData(GlobalConstants.REQ_APP_URL, ApplicationUrl);
            }


            m_requestStreamActions.Enqueue(stream =>
                                               {
                                                   byte[] endBoundaryBytes = Encoding.UTF8.GetBytes(END_FORM_BOUNDARY);
                                                   stream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
                                               });

            var reqStream = m_webRequest.GetRequestStream();
            
            try
            {
               
                m_requestStreamActions.ToList().ForEach(act => act(reqStream));

            }
            finally 
            {
                

                reqStream.Close();
            }
            
        }

        private void checkRequestNotNull()
        {
            if (m_webRequest == null)
            {
                throw new InvalidOperationException();
            }
        }

        private void writeMediaName(string fileName, Stream stream)
        {
            writeBoundaryBegin(stream);
            string fileHeader = String.Format(FORM_FILE_PATTERN, fileName);
            byte[] headerBytes = Encoding.UTF8.GetBytes(fileHeader);
            stream.Write(headerBytes, 0, headerBytes.Length);
        }

        #endregion methods

        #region Implementation of IRawRequestResponsePublisher

        public event EventHandler<EventArgsValue<WebRequest>> WebRequestCreated;
        public event EventHandler<EventArgsValue<WebResponse>> WebResponseCreated;

        #endregion

        #region Implementation of IExtensionInterface

        public virtual T GetInterface<T>() where T : class
        {
            return this as T;
        }

        #endregion
    }
}